Load and clean data

cloud_data_orig <- load_cloud_data()
cloud_data_ls <- clean_cloud_data(cloud_data_orig)
cloud_data <- dplyr::bind_rows(cloud_data_ls, .id = "image")

Exploratory Data Analysis

Raw Images

raw_features <- c("DF", "CF", "BF", "AF", "AN")
for (var in c("label", raw_features)) {
  cat(sprintf("\n\n### %s\n\n", var))
  plt <- plot_cloud_data(cloud_data, var)
  vthemes::subchunkify(
    plt, i = subchunk_idx, fig_width = 10, fig_height = 4
  )
  subchunk_idx <- subchunk_idx + 1
}

label

DF

CF

BF

AF

AN

Correlations

for (image_id in unique(cloud_data$image)) {
  plt_df <- cloud_data |> 
    dplyr::filter(image == !!image_id)
  plt <- plot_pairs(
    plt_df, columns = c("DF", "CF", "BF", "AF", "AN"), 
    point_size = 0.1, subsample = 0.5, color = plt_df$label
  ) +
    ggplot2::scale_color_manual(values = cloud_colors) +
    ggplot2::scale_fill_manual(values = cloud_colors) +
    ggplot2::labs(title = sprintf("Image %s", image_id))
  vthemes::subchunkify(
    plt, i = subchunk_idx, fig_width = 10, fig_height = 10
  )
  subchunk_idx <- subchunk_idx + 1
}

Feature Engineering

engineered_features <- c("NDAI", "SD", "CORR")
for (var in c("label", engineered_features)) {
  cat(sprintf("\n\n### %s\n\n", var))
  plt <- plot_cloud_data(cloud_data, var)
  vthemes::subchunkify(
    plt, i = subchunk_idx, fig_width = 10, fig_height = 4
  )
  subchunk_idx <- subchunk_idx + 1
}

label

NDAI

SD

CORR

Prediction Modeling

Data Splitting

To mimic the process of how we obtain our future data (in this case, we expect to obtain completely new images), we leave out one full image for the test set. For cross-validation and the training-validation splits, we also perform clustered sampling, where we partition the images into contiguous blocks to maintain the integrity of the image. Below, we show the image blocks used in the data splitting scheme.

# divide image into contiguous chunks
cloud_data <- add_cloud_blocks(cloud_data_ls)
plt <- plot_cloud_data(cloud_data, var = "block_id")
plt

# save one image for testing
test_image_idx <- sample(unique(cloud_data$image), 1)
train_data_all <- cloud_data |>
  dplyr::filter(!(image %in% test_image_idx))
test_data_all <- cloud_data |>
  dplyr::filter(image %in% test_image_idx)
print(sprintf("Test image: %s", test_image_idx))
## [1] "Test image: 1"

Modeling

We next use the aforementioned data splitting scheme to both tune model hyperparameters and select the best model from the following candidates:

Models under consideration:

  • Logistic regression
  • Lasso regression (needs tuning)
  • Ridge regression (needs tuning)
  • Random forest (no tuning; using default hyperparameters)

Note that within glmnet::cv.glmnet, there is an interior cross-validation loop to tune the hyperparameters for LASSO and ridge, and by explicitly setting the foldid, we ensure that the cross-validation is done using our clustered sampling scheme by image block. If the R function doesn’t have a built-in CV option, we would need to code this up ourselves (or using other functions like from the caret R package). Within glmnet::cv.glmnet, there is also a re-fitting step, where the best hyperparameters are used to fit the model on the full training set.

We will also include the engineered features (NDAI, SD, CORR) since we’ve seen these to be quite informative for predicting cloud cover.

keep_vars <- c("NDAI", "SD", "CORR", "DF", "CF", "BF", "AF", "AN", "binary_label")
train_data <- cloud_data |>
  dplyr::filter(!(image %in% test_image_idx))
test_data <- cloud_data |>
  dplyr::filter(image %in% test_image_idx)

# evaluate validation error for various models
valid_fits_ls <- list()
valid_X_sds_ls <- list()
valid_auroc_ls <- list()
valid_auprc_ls <- list()
valid_preds_ls <- list()
for (fold in unique(train_data_all$block_id)) {
  # do data split
  cv_train_data_all <- train_data_all |> 
    dplyr::filter(block_id != !!fold)
  cv_train_data <- cv_train_data_all |> 
    dplyr::select(tidyselect::all_of(keep_vars))
  cv_valid_data_all <- train_data_all |> 
    dplyr::filter(block_id == !!fold)
  cv_valid_data <- cv_valid_data_all |> 
    dplyr::select(tidyselect::all_of(keep_vars))
  
  # fit logistic regression
  log_fit <- glm(
    binary_label ~ ., data = cv_train_data, family = "binomial"
  )
  log_preds <- predict(log_fit, cv_valid_data, type = "response")
  
  # fit and evaluate lasso regression
  lasso_fit <- glmnet::cv.glmnet(
    x = as.matrix(cv_train_data |> dplyr::select(-binary_label)),
    y = cv_train_data$binary_label,
    family = "binomial",
    alpha = 1,
    foldid = as.numeric(as.factor(cv_train_data_all$block_id))
  )
  lasso_preds <- predict(
    lasso_fit, 
    as.matrix(cv_valid_data |> dplyr::select(-binary_label)), 
    s = "lambda.min", # or "lambda.1se"
    type = "response"
  )
  
  # fit and evaluate ridge regression
  ridge_fit <- glmnet::cv.glmnet(
    x = as.matrix(cv_train_data |> dplyr::select(-binary_label)),
    y = cv_train_data$binary_label,
    family = "binomial",
    alpha = 0,
    foldid = as.numeric(as.factor(cv_train_data_all$block_id))
  )
  ridge_preds <- predict(
    ridge_fit, 
    as.matrix(cv_valid_data |> dplyr::select(-binary_label)), 
    s = "lambda.min", # or "lambda.1se"
    type = "response"
  )
  
  # fit random forest
  rf_fit <- ranger::ranger(
    binary_label ~ ., data = cv_train_data, 
    importance = "impurity", # to compute MDI importance
    probability = TRUE, verbose = FALSE
  )
  rf_preds <- predict(rf_fit, cv_valid_data)$predictions[, 2]
  
  # evaluate predictions
  preds_ls <- list(
    "logistic" = log_preds,
    "lasso" = lasso_preds,
    "ridge" = ridge_preds,
    "rf" = rf_preds
  )
  valid_auroc_ls[[fold]] <- purrr::map(
    preds_ls,
    ~ yardstick::roc_auc_vec(
      truth = cv_valid_data$binary_label, 
      estimate = c(.x), 
      event_level = "second"
    )
  ) |>
    dplyr::bind_rows(.id = "method")
  valid_auprc_ls[[fold]] <- purrr::map(
    preds_ls,
    ~ yardstick::pr_auc_vec(
      truth = cv_valid_data$binary_label, 
      estimate = c(.x), 
      event_level = "second"
    )
  ) |>
    dplyr::bind_rows(.id = "method")
  
  # save fold predictions for future investigation
  valid_preds_ls[[fold]] <- cv_valid_data_all |> 
    dplyr::bind_cols(preds_ls)
  
  # save fitted models
  valid_fits_ls[[fold]] <- list(
    "logistic" = log_fit,
    "lasso" = lasso_fit,
    "ridge" = ridge_fit,
    "rf" = rf_fit
  )
  
  # save X standard deviations to normalize ridge/lasso coefficients later
  valid_X_sds_ls[[fold]] <- cv_train_data |> 
    dplyr::select(-binary_label) |> 
    apply(2, sd)
}

# examine validation accuracy
valid_auroc_df <- dplyr::bind_rows(valid_auroc_ls, .id = "fold")
mean_valid_auroc_df <- valid_auroc_df |> 
  dplyr::summarise(dplyr::across(-fold, ~ mean(.x, na.rm = TRUE)))
valid_auprc_df <- dplyr::bind_rows(valid_auprc_ls, .id = "fold")
mean_valid_auprc_df <- valid_auprc_df |> 
  dplyr::summarise(dplyr::across(-fold, ~ mean(.x, na.rm = TRUE)))

# evaluate best model on test set
train_data <- train_data_all |> 
  dplyr::select(tidyselect::all_of(keep_vars))
test_data <- test_data_all |>
  dplyr::select(tidyselect::all_of(keep_vars))
best_fit <- ranger::ranger(
  binary_label ~ ., data = train_data, probability = TRUE, verbose = FALSE
)
test_preds <- predict(best_fit, test_data)$predictions[, 2]
test_auroc <- yardstick::roc_auc_vec(
  truth = test_data$binary_label, 
  estimate = test_preds, 
  event_level = "second"
)
test_auprc <- yardstick::pr_auc_vec(
  truth = test_data$binary_label, 
  estimate = test_preds, 
  event_level = "second"
)

auroc_df <- mean_valid_auroc_df |>
  dplyr::rename_with(~ sprintf("Validation %s AUROC", stringr::str_to_title(.x))) |> 
  dplyr::mutate(
    `Test AUROC` = test_auroc
  )
auprc_df <- mean_valid_auprc_df |>
  dplyr::rename_with(~ sprintf("Validation %s AUPRC", stringr::str_to_title(.x))) |> 
  dplyr::mutate(
    `Test AUPRC` = test_auprc
  )

vthemes::pretty_kable(
  valid_auroc_df, caption = "Fold-wise Validation AUROC for Various Models"
)
Table 1: Fold-wise Validation AUROC for Various Models
fold logistic lasso ridge rf
6 0.875 0.875 0.880 0.912
5 0.820 0.821 0.827 0.791
7 0.876 0.874 0.862 0.919
8 0.945 0.945 0.955 0.950
10 0.430 0.427 0.383 0.632
9 0.732 0.733 0.726 0.673
11 0.342 0.327 0.244 0.832
12 0.788 0.786 0.763 0.902
vthemes::pretty_kable(
  auroc_df, caption = "Overall AUROC Prediction Performance"
)
Table 1: Overall AUROC Prediction Performance
Validation Logistic AUROC Validation Lasso AUROC Validation Ridge AUROC Validation Rf AUROC Test AUROC
0.726 0.724 0.705 0.826 0.821
vthemes::pretty_kable(
  valid_auprc_df, caption = "Fold-wise Validation AUPRC for Various Models"
)
Table 1: Fold-wise Validation AUPRC for Various Models
fold logistic lasso ridge rf
6 0.759 0.759 0.767 0.820
5 0.963 0.964 0.965 0.948
7 0.365 0.362 0.336 0.508
8 0.502 0.504 0.512 0.523
10 0.344 0.343 0.324 0.571
9 0.379 0.379 0.358 0.356
11 0.0168 0.0159 0.0132 0.0713
12 0.242 0.238 0.203 0.601
vthemes::pretty_kable(
  auprc_df, caption = "Overall AUPRC Prediction Performance"
)
Table 1: Overall AUPRC Prediction Performance
Validation Logistic AUPRC Validation Lasso AUPRC Validation Ridge AUPRC Validation Rf AUPRC Test AUPRC
0.446 0.445 0.435 0.550 0.400

Post-hoc investigations

Let’s look at the held-out folds where the methods didn’t perform so well and compare the predictions across methods.

plot_vars <- c(
  "binary_label", "logistic", "lasso", "ridge", "rf",
  "DF", "CF", "BF", "AF", "AN", "NDAI", "SD", "CORR"
)
for (fold in names(valid_preds_ls)) {
  cat(sprintf("\n\n#### Fold %s\n\n", fold))
  preds_df <- valid_preds_ls[[fold]]
  plt_ls <- list()
  for (var in plot_vars) {
    plt_ls[[var]] <- plot_cloud_data(preds_df, var)
  }
  plt <- patchwork::wrap_plots(plt_ls, ncol = 5)
  vthemes::subchunkify(
    plt, i = subchunk_idx, fig_width = 14, fig_height = 6
  )
  subchunk_idx <- subchunk_idx + 1
}

Fold 6

Fold 5

Fold 7

Fold 8

Fold 10

Fold 9

Fold 11

Fold 12

Interpretations

fi_ls <- list()
for (fold in names(valid_fits_ls)) {
  # get feature importances from each method
  log_fit <- valid_fits_ls[[fold]]$logistic
  log_fi_df1 <- tibble::tibble(
    method = "logistic (coef)",
    var = names(coef(log_fit))[-1], # remove first element (i.e., intercept)
    importance = coef(log_fit)[-1]
  )
  log_fi_df2 <- tibble::tibble(
    method = "logistic (z stat)",
    var = names(summary(log_fit)$coefficients[, "z value"])[-1],
    importance = summary(log_fit)$coefficients[, "z value"][-1],
  )
  
  lasso_fit <- valid_fits_ls[[fold]]$lasso
  lasso_fi_df1 <- tibble::tibble(
    method = "lasso (coef)",
    var = rownames(coef(lasso_fit, s = "lambda.min"))[-1],
    importance = as.matrix(coef(lasso_fit, s = "lambda.min"))[-1]
  )
  lasso_fi_df2 <- tibble::tibble(
    method = "lasso (std. coef)",
    var = rownames(coef(lasso_fit, s = "lambda.min"))[-1],
    importance = as.matrix(coef(lasso_fit, s = "lambda.min"))[-1] *
      valid_X_sds_ls[[fold]]
  )
  
  ridge_fit <- valid_fits_ls[[fold]]$ridge
  ridge_fi_df1 <- tibble::tibble(
    method = "ridge (coef)",
    var = rownames(coef(ridge_fit, s = "lambda.min"))[-1],
    importance = as.matrix(coef(ridge_fit, s = "lambda.min"))[-1]
  )
  ridge_fi_df2 <- tibble::tibble(
    method = "ridge (std. coef)",
    var = rownames(coef(ridge_fit, s = "lambda.min"))[-1],
    importance = as.matrix(coef(ridge_fit, s = "lambda.min"))[-1] *
      valid_X_sds_ls[[fold]]
  )
  
  rf_fit <- valid_fits_ls[[fold]]$rf
  rf_fi_df1 <- tibble::tibble(
    method = "rf (mdi)",
    var = names(rf_fit$variable.importance),
    importance = rf_fit$variable.importance
  )
  # compute permutation importance using held-out fold (not training folds)
  X_valid_df <- train_data_all |>
    dplyr::filter(block_id == !!fold) |>
    dplyr::select(tidyselect::all_of(keep_vars))
  pfun <- function(object, newdata) {
    # Needs to return vector of class predictions from a ranger object (if using metric = "accuracy")
    ifelse(
      predict(object, data = newdata)$predictions[, 2] >= 0.5,
      "Clouds",
      "No Clouds"
    ) |>
      factor(levels = c("No Clouds", "Clouds"))
  }
  permute_imp <- vip::vi(
    rf_fit, method = "permute", train = X_valid_df, target = "binary_label",
    metric = "accuracy", pred_wrapper = pfun, nsim = 10
  )
  rf_fi_df2 <- tibble::tibble(
    method = "rf (permutation)",
    var = permute_imp$Variable,
    importance = permute_imp$Importance
  )
  
  fi_ls[[fold]] <- dplyr::bind_rows(
    log_fi_df1, log_fi_df2, 
    lasso_fi_df1, lasso_fi_df2, 
    ridge_fi_df1, ridge_fi_df2, 
    rf_fi_df1, rf_fi_df2
  )
}

fi_df <- dplyr::bind_rows(fi_ls, .id = "fold") |> 
  dplyr::mutate(
    var = factor(var, levels = keep_vars)
  )

for (method_name in c("logistic", "lasso", "ridge", "rf")) {
  cat(
    sprintf("\n\n## %s {.tabset .tabset-pills .tabset-pills-square}\n\n", method_name)
  )
  
  plt_df <- fi_df |> 
    dplyr::filter(
      stringr::str_starts(method, !!method_name)
    )
  
  # bar plot
  if (method_name %in% c("logistic", "lasso", "ridge")) {
    plt <- plt_df |> 
      dplyr::group_by(method, var) |> 
      dplyr::summarise(
        mean_importance = mean(importance),
        se_importance = sd(importance) / sqrt(dplyr::n()),
        .groups = "drop"
      ) |> 
      dplyr::filter(
        stringr::str_detect(method, "\\(coef\\)")
      ) |> 
      ggplot2::ggplot() +
      ggplot2::geom_bar(
        ggplot2::aes(x = var, y = mean_importance),
        stat = "identity"
      ) +
      ggplot2::geom_errorbar(
        ggplot2::aes(
          x = var, 
          ymin = mean_importance - se_importance,
          ymax = mean_importance + se_importance
        ),
        width = 0
      ) +
      ggplot2::facet_wrap(~ method, ncol = 1, scales = "free_y") +
      ggplot2::labs(x = "Feature", y = "Mean Importance") +
      vthemes::theme_vmodern()
    
    vthemes::subchunkify(
      plt, i = subchunk_idx, fig_width = 12, fig_height = 4
    )
    subchunk_idx <- subchunk_idx + 1
  }
  
  # bar plot
  plt <- plt_df |> 
    dplyr::group_by(method, var) |> 
    dplyr::summarise(
      mean_importance = mean(importance),
      se_importance = sd(importance) / sqrt(dplyr::n()),
      .groups = "drop"
    ) |> 
    ggplot2::ggplot() +
    ggplot2::geom_bar(
      ggplot2::aes(x = var, y = mean_importance),
      stat = "identity"
    ) +
    ggplot2::geom_errorbar(
      ggplot2::aes(
        x = var, 
        ymin = mean_importance - se_importance,
        ymax = mean_importance + se_importance
      ),
      width = 0
    ) +
    ggplot2::facet_wrap(~ method, ncol = 1, scales = "free_y") +
    ggplot2::labs(x = "Feature", y = "Mean Importance") +
    vthemes::theme_vmodern()
  vthemes::subchunkify(
    plotly::ggplotly(plt), i = subchunk_idx, fig_width = 12, fig_height = 8
  )
  subchunk_idx <- subchunk_idx + 1
  
  # boxplot
  plt <- plt_df |> 
    ggplot2::ggplot() +
    ggplot2::geom_boxplot(
      ggplot2::aes(x = var, y = importance)
    ) +
    ggplot2::facet_wrap(~ method, ncol = 1, scales = "free_y") +
    ggplot2::labs(x = "Feature", y = "Importance") +
    vthemes::theme_vmodern()
  vthemes::subchunkify(
    plt, i = subchunk_idx, fig_width = 12, fig_height = 10
  )
  subchunk_idx <- subchunk_idx + 1
}

logistic

lasso

ridge

rf

# summary of (normalized) feature importance results without absolute value
cat("\n\n## Summary {.tabset .tabset-pills .tabset-pills-square}\n\n")

Summary

keep_methods <- c(
  "logistic (z stat)", "lasso (std. coef)", "ridge (std. coef)", "rf (mdi)", "rf (permutation)"
)
plt <- fi_df |> 
  dplyr::filter(
    method %in% !!keep_methods
  ) |> 
  dplyr::mutate(
    method = factor(method, levels = keep_methods)
  ) |> 
  dplyr::group_by(method, var) |> 
  dplyr::summarise(
    mean_importance = mean(importance),
    se_importance = sd(importance) / sqrt(dplyr::n()),
    .groups = "drop"
  ) |> 
  ggplot2::ggplot() +
  ggplot2::geom_bar(
    ggplot2::aes(x = var, y = mean_importance),
    stat = "identity"
  ) +
  ggplot2::geom_errorbar(
    ggplot2::aes(
      x = var, 
      ymin = mean_importance - se_importance,
      ymax = mean_importance + se_importance
    ),
    width = 0
  ) +
  ggplot2::facet_wrap(~ method, ncol = 1, scales = "free_y") +
  ggplot2::labs(x = "Feature", y = "Mean Importance") +
  vthemes::theme_vmodern()
vthemes::subchunkify(
  plotly::ggplotly(plt), i = subchunk_idx, fig_width = 12, fig_height = 12
)
subchunk_idx <- subchunk_idx + 1

# summary of (normalized) feature importance results with absolute value
cat("\n\n## Summary (magnitudes only) {.tabset .tabset-pills .tabset-pills-square}\n\n")

Summary (magnitudes only)

keep_method_levels <- stringr::str_replace_all(keep_methods, "std. coef", "|std. coef|")
plt <- fi_df |> 
  dplyr::filter(
    method %in% !!keep_methods
  ) |> 
  dplyr::mutate(
    importance = dplyr::case_when(
      stringr::str_detect(method, "logistic|lasso|ridge") ~ abs(importance),
      TRUE ~ importance
    ),
    method = stringr::str_replace_all(method, "std. coef", "|std. coef|") |> 
      factor(levels = keep_method_levels)
  ) |> 
  dplyr::group_by(method, var) |> 
  dplyr::summarise(
    mean_importance = mean(importance),
    se_importance = sd(importance) / sqrt(dplyr::n()),
    .groups = "drop"
  ) |> 
  ggplot2::ggplot() +
  ggplot2::geom_bar(
    ggplot2::aes(x = var, y = mean_importance),
    stat = "identity"
  ) +
  ggplot2::geom_errorbar(
    ggplot2::aes(
      x = var, 
      ymin = mean_importance - se_importance,
      ymax = mean_importance + se_importance
    ),
    width = 0
  ) +
  ggplot2::facet_wrap(~ method, ncol = 1, scales = "free_y") +
  ggplot2::labs(x = "Feature", y = "Mean Importance") +
  vthemes::theme_vmodern()
vthemes::subchunkify(
  plotly::ggplotly(plt), i = subchunk_idx, fig_width = 12, fig_height = 12
)
subchunk_idx <- subchunk_idx + 1
LS0tCnRpdGxlOiAiU3VwZXJ2aXNlZCBMZWFybmluZyB3aXRoIENsb3VkcyBEYXRhIgphdXRob3I6ICJUaWZmYW55IFRhbmciCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OiAKICB2dGhlbWVzOjp2bW9kZXJuOgogICAgY29kZV9mb2xkaW5nOiBzaG93Ci0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBlY2hvID0gVFJVRSwKICB3YXJuaW5nID0gRkFMU0UsCiAgbWVzc2FnZSA9IEZBTFNFCikKCnNvdXJjZShoZXJlOjpoZXJlKCJSIiwgImxvYWQuUiIpKQpzb3VyY2UoaGVyZTo6aGVyZSgiUiIsICJjbGVhbi5SIikpCnNvdXJjZShoZXJlOjpoZXJlKCJSIiwgInBsb3QuUiIpKQoKc3ViY2h1bmtfaWR4IDwtIDEKc2V0LnNlZWQoMjQyKQoKY2xvdWRfY29sb3JzIDwtIGMoImRhcmsgZ3JlZW4iLCAibGlnaHQgYmx1ZSIsICJibGFjayIpCmBgYAoKIyBMb2FkIGFuZCBjbGVhbiBkYXRhIHsudGFic2V0IC50YWJzZXQtdm1vZGVybn0KCmBgYHtyfQpjbG91ZF9kYXRhX29yaWcgPC0gbG9hZF9jbG91ZF9kYXRhKCkKY2xvdWRfZGF0YV9scyA8LSBjbGVhbl9jbG91ZF9kYXRhKGNsb3VkX2RhdGFfb3JpZykKY2xvdWRfZGF0YSA8LSBkcGx5cjo6YmluZF9yb3dzKGNsb3VkX2RhdGFfbHMsIC5pZCA9ICJpbWFnZSIpCmBgYAoKIyBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzIHsudGFic2V0IC50YWJzZXQtdm1vZGVybn0KCiMjIFJhdyBJbWFnZXMgey50YWJzZXQgLnRhYnNldC1waWxscyAudGFic2V0LXNxdWFyZX0KCmBgYHtyIHJlc3VsdHMgPSAiYXNpcyJ9CnJhd19mZWF0dXJlcyA8LSBjKCJERiIsICJDRiIsICJCRiIsICJBRiIsICJBTiIpCmZvciAodmFyIGluIGMoImxhYmVsIiwgcmF3X2ZlYXR1cmVzKSkgewogIGNhdChzcHJpbnRmKCJcblxuIyMjICVzXG5cbiIsIHZhcikpCiAgcGx0IDwtIHBsb3RfY2xvdWRfZGF0YShjbG91ZF9kYXRhLCB2YXIpCiAgdnRoZW1lczo6c3ViY2h1bmtpZnkoCiAgICBwbHQsIGkgPSBzdWJjaHVua19pZHgsIGZpZ193aWR0aCA9IDEwLCBmaWdfaGVpZ2h0ID0gNAogICkKICBzdWJjaHVua19pZHggPC0gc3ViY2h1bmtfaWR4ICsgMQp9CmBgYAoKIyMgQ29ycmVsYXRpb25zIHsudGFic2V0IC50YWJzZXQtcGlsbHMgLnRhYnNldC1zcXVhcmV9CgpgYGB7ciByZXN1bHRzID0gImFzaXMifQpmb3IgKGltYWdlX2lkIGluIHVuaXF1ZShjbG91ZF9kYXRhJGltYWdlKSkgewogIHBsdF9kZiA8LSBjbG91ZF9kYXRhIHw+IAogICAgZHBseXI6OmZpbHRlcihpbWFnZSA9PSAhIWltYWdlX2lkKQogIHBsdCA8LSBwbG90X3BhaXJzKAogICAgcGx0X2RmLCBjb2x1bW5zID0gYygiREYiLCAiQ0YiLCAiQkYiLCAiQUYiLCAiQU4iKSwgCiAgICBwb2ludF9zaXplID0gMC4xLCBzdWJzYW1wbGUgPSAwLjUsIGNvbG9yID0gcGx0X2RmJGxhYmVsCiAgKSArCiAgICBnZ3Bsb3QyOjpzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY2xvdWRfY29sb3JzKSArCiAgICBnZ3Bsb3QyOjpzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjbG91ZF9jb2xvcnMpICsKICAgIGdncGxvdDI6OmxhYnModGl0bGUgPSBzcHJpbnRmKCJJbWFnZSAlcyIsIGltYWdlX2lkKSkKICB2dGhlbWVzOjpzdWJjaHVua2lmeSgKICAgIHBsdCwgaSA9IHN1YmNodW5rX2lkeCwgZmlnX3dpZHRoID0gMTAsIGZpZ19oZWlnaHQgPSAxMAogICkKICBzdWJjaHVua19pZHggPC0gc3ViY2h1bmtfaWR4ICsgMQp9CmBgYAoKIyMgRmVhdHVyZSBFbmdpbmVlcmluZyB7LnRhYnNldCAudGFic2V0LXBpbGxzIC50YWJzZXQtc3F1YXJlfQoKYGBge3IgcmVzdWx0cyA9ICJhc2lzIn0KZW5naW5lZXJlZF9mZWF0dXJlcyA8LSBjKCJOREFJIiwgIlNEIiwgIkNPUlIiKQpmb3IgKHZhciBpbiBjKCJsYWJlbCIsIGVuZ2luZWVyZWRfZmVhdHVyZXMpKSB7CiAgY2F0KHNwcmludGYoIlxuXG4jIyMgJXNcblxuIiwgdmFyKSkKICBwbHQgPC0gcGxvdF9jbG91ZF9kYXRhKGNsb3VkX2RhdGEsIHZhcikKICB2dGhlbWVzOjpzdWJjaHVua2lmeSgKICAgIHBsdCwgaSA9IHN1YmNodW5rX2lkeCwgZmlnX3dpZHRoID0gMTAsIGZpZ19oZWlnaHQgPSA0CiAgKQogIHN1YmNodW5rX2lkeCA8LSBzdWJjaHVua19pZHggKyAxCn0KYGBgCgojIFByZWRpY3Rpb24gTW9kZWxpbmcgey50YWJzZXQgLnRhYnNldC12bW9kZXJufQoKIyMgRGF0YSBTcGxpdHRpbmcgCgo8ZGl2IGNsYXNzPSJwYW5lbCBwYW5lbC1kZWZhdWx0IHBhZGRlZC1wYW5lbCI+ClRvIG1pbWljIHRoZSBwcm9jZXNzIG9mIGhvdyB3ZSBvYnRhaW4gb3VyIGZ1dHVyZSBkYXRhIChpbiB0aGlzIGNhc2UsIHdlIGV4cGVjdCB0byBvYnRhaW4gY29tcGxldGVseSBuZXcgaW1hZ2VzKSwgd2UgbGVhdmUgb3V0IG9uZSBmdWxsIGltYWdlIGZvciB0aGUgdGVzdCBzZXQuIEZvciBjcm9zcy12YWxpZGF0aW9uIGFuZCB0aGUgdHJhaW5pbmctdmFsaWRhdGlvbiBzcGxpdHMsIHdlIGFsc28gcGVyZm9ybSBjbHVzdGVyZWQgc2FtcGxpbmcsIHdoZXJlIHdlIHBhcnRpdGlvbiB0aGUgaW1hZ2VzIGludG8gY29udGlndW91cyBibG9ja3MgdG8gbWFpbnRhaW4gdGhlIGludGVncml0eSBvZiB0aGUgaW1hZ2UuIEJlbG93LCB3ZSBzaG93IHRoZSBpbWFnZSBibG9ja3MgdXNlZCBpbiB0aGUgZGF0YSBzcGxpdHRpbmcgc2NoZW1lLgo8L2Rpdj4KCmBgYHtyfQojIGRpdmlkZSBpbWFnZSBpbnRvIGNvbnRpZ3VvdXMgY2h1bmtzCmNsb3VkX2RhdGEgPC0gYWRkX2Nsb3VkX2Jsb2NrcyhjbG91ZF9kYXRhX2xzKQpwbHQgPC0gcGxvdF9jbG91ZF9kYXRhKGNsb3VkX2RhdGEsIHZhciA9ICJibG9ja19pZCIpCnBsdApgYGAKCmBgYHtyfQojIHNhdmUgb25lIGltYWdlIGZvciB0ZXN0aW5nCnRlc3RfaW1hZ2VfaWR4IDwtIHNhbXBsZSh1bmlxdWUoY2xvdWRfZGF0YSRpbWFnZSksIDEpCnRyYWluX2RhdGFfYWxsIDwtIGNsb3VkX2RhdGEgfD4KICBkcGx5cjo6ZmlsdGVyKCEoaW1hZ2UgJWluJSB0ZXN0X2ltYWdlX2lkeCkpCnRlc3RfZGF0YV9hbGwgPC0gY2xvdWRfZGF0YSB8PgogIGRwbHlyOjpmaWx0ZXIoaW1hZ2UgJWluJSB0ZXN0X2ltYWdlX2lkeCkKcHJpbnQoc3ByaW50ZigiVGVzdCBpbWFnZTogJXMiLCB0ZXN0X2ltYWdlX2lkeCkpCmBgYAoKIyMgTW9kZWxpbmcKCjxkaXYgY2xhc3M9InBhbmVsIHBhbmVsLWRlZmF1bHQgcGFkZGVkLXBhbmVsIj4KV2UgbmV4dCB1c2UgdGhlIGFmb3JlbWVudGlvbmVkIGRhdGEgc3BsaXR0aW5nIHNjaGVtZSB0byBib3RoIHR1bmUgbW9kZWwgaHlwZXJwYXJhbWV0ZXJzIGFuZCBzZWxlY3QgdGhlIGJlc3QgbW9kZWwgZnJvbSB0aGUgZm9sbG93aW5nIGNhbmRpZGF0ZXM6CgoqKk1vZGVscyB1bmRlciBjb25zaWRlcmF0aW9uOioqCgotIExvZ2lzdGljIHJlZ3Jlc3Npb24KLSBMYXNzbyByZWdyZXNzaW9uIChuZWVkcyB0dW5pbmcpCi0gUmlkZ2UgcmVncmVzc2lvbiAobmVlZHMgdHVuaW5nKQotIFJhbmRvbSBmb3Jlc3QgKG5vIHR1bmluZzsgdXNpbmcgZGVmYXVsdCBoeXBlcnBhcmFtZXRlcnMpCgpOb3RlIHRoYXQgd2l0aGluIGBnbG1uZXQ6OmN2LmdsbW5ldGAsIHRoZXJlIGlzIGFuIGludGVyaW9yIGNyb3NzLXZhbGlkYXRpb24gbG9vcCB0byB0dW5lIHRoZSBoeXBlcnBhcmFtZXRlcnMgZm9yIExBU1NPIGFuZCByaWRnZSwgYW5kIGJ5IGV4cGxpY2l0bHkgc2V0dGluZyB0aGUgYGZvbGRpZGAsIHdlIGVuc3VyZSB0aGF0IHRoZSBjcm9zcy12YWxpZGF0aW9uIGlzIGRvbmUgdXNpbmcgb3VyIGNsdXN0ZXJlZCBzYW1wbGluZyBzY2hlbWUgYnkgaW1hZ2UgYmxvY2suIElmIHRoZSBSIGZ1bmN0aW9uIGRvZXNuJ3QgaGF2ZSBhIGJ1aWx0LWluIENWIG9wdGlvbiwgd2Ugd291bGQgbmVlZCB0byBjb2RlIHRoaXMgdXAgb3Vyc2VsdmVzIChvciB1c2luZyBvdGhlciBmdW5jdGlvbnMgbGlrZSBmcm9tIHRoZSBgY2FyZXRgIFIgcGFja2FnZSkuIFdpdGhpbiBgZ2xtbmV0Ojpjdi5nbG1uZXRgLCB0aGVyZSBpcyBhbHNvIGEgcmUtZml0dGluZyBzdGVwLCB3aGVyZSB0aGUgYmVzdCBoeXBlcnBhcmFtZXRlcnMgYXJlIHVzZWQgdG8gZml0IHRoZSBtb2RlbCBvbiB0aGUgZnVsbCB0cmFpbmluZyBzZXQuCgpXZSB3aWxsIGFsc28gaW5jbHVkZSB0aGUgZW5naW5lZXJlZCBmZWF0dXJlcyAoTkRBSSwgU0QsIENPUlIpIHNpbmNlIHdlJ3ZlIHNlZW4gdGhlc2UgdG8gYmUgcXVpdGUgaW5mb3JtYXRpdmUgZm9yIHByZWRpY3RpbmcgY2xvdWQgY292ZXIuCjwvZGl2PgoKYGBge3J9CmtlZXBfdmFycyA8LSBjKCJOREFJIiwgIlNEIiwgIkNPUlIiLCAiREYiLCAiQ0YiLCAiQkYiLCAiQUYiLCAiQU4iLCAiYmluYXJ5X2xhYmVsIikKdHJhaW5fZGF0YSA8LSBjbG91ZF9kYXRhIHw+CiAgZHBseXI6OmZpbHRlcighKGltYWdlICVpbiUgdGVzdF9pbWFnZV9pZHgpKQp0ZXN0X2RhdGEgPC0gY2xvdWRfZGF0YSB8PgogIGRwbHlyOjpmaWx0ZXIoaW1hZ2UgJWluJSB0ZXN0X2ltYWdlX2lkeCkKCiMgZXZhbHVhdGUgdmFsaWRhdGlvbiBlcnJvciBmb3IgdmFyaW91cyBtb2RlbHMKdmFsaWRfZml0c19scyA8LSBsaXN0KCkKdmFsaWRfWF9zZHNfbHMgPC0gbGlzdCgpCnZhbGlkX2F1cm9jX2xzIDwtIGxpc3QoKQp2YWxpZF9hdXByY19scyA8LSBsaXN0KCkKdmFsaWRfcHJlZHNfbHMgPC0gbGlzdCgpCmZvciAoZm9sZCBpbiB1bmlxdWUodHJhaW5fZGF0YV9hbGwkYmxvY2tfaWQpKSB7CiAgIyBkbyBkYXRhIHNwbGl0CiAgY3ZfdHJhaW5fZGF0YV9hbGwgPC0gdHJhaW5fZGF0YV9hbGwgfD4gCiAgICBkcGx5cjo6ZmlsdGVyKGJsb2NrX2lkICE9ICEhZm9sZCkKICBjdl90cmFpbl9kYXRhIDwtIGN2X3RyYWluX2RhdGFfYWxsIHw+IAogICAgZHBseXI6OnNlbGVjdCh0aWR5c2VsZWN0OjphbGxfb2Yoa2VlcF92YXJzKSkKICBjdl92YWxpZF9kYXRhX2FsbCA8LSB0cmFpbl9kYXRhX2FsbCB8PiAKICAgIGRwbHlyOjpmaWx0ZXIoYmxvY2tfaWQgPT0gISFmb2xkKQogIGN2X3ZhbGlkX2RhdGEgPC0gY3ZfdmFsaWRfZGF0YV9hbGwgfD4gCiAgICBkcGx5cjo6c2VsZWN0KHRpZHlzZWxlY3Q6OmFsbF9vZihrZWVwX3ZhcnMpKQogIAogICMgZml0IGxvZ2lzdGljIHJlZ3Jlc3Npb24KICBsb2dfZml0IDwtIGdsbSgKICAgIGJpbmFyeV9sYWJlbCB+IC4sIGRhdGEgPSBjdl90cmFpbl9kYXRhLCBmYW1pbHkgPSAiYmlub21pYWwiCiAgKQogIGxvZ19wcmVkcyA8LSBwcmVkaWN0KGxvZ19maXQsIGN2X3ZhbGlkX2RhdGEsIHR5cGUgPSAicmVzcG9uc2UiKQogIAogICMgZml0IGFuZCBldmFsdWF0ZSBsYXNzbyByZWdyZXNzaW9uCiAgbGFzc29fZml0IDwtIGdsbW5ldDo6Y3YuZ2xtbmV0KAogICAgeCA9IGFzLm1hdHJpeChjdl90cmFpbl9kYXRhIHw+IGRwbHlyOjpzZWxlY3QoLWJpbmFyeV9sYWJlbCkpLAogICAgeSA9IGN2X3RyYWluX2RhdGEkYmluYXJ5X2xhYmVsLAogICAgZmFtaWx5ID0gImJpbm9taWFsIiwKICAgIGFscGhhID0gMSwKICAgIGZvbGRpZCA9IGFzLm51bWVyaWMoYXMuZmFjdG9yKGN2X3RyYWluX2RhdGFfYWxsJGJsb2NrX2lkKSkKICApCiAgbGFzc29fcHJlZHMgPC0gcHJlZGljdCgKICAgIGxhc3NvX2ZpdCwgCiAgICBhcy5tYXRyaXgoY3ZfdmFsaWRfZGF0YSB8PiBkcGx5cjo6c2VsZWN0KC1iaW5hcnlfbGFiZWwpKSwgCiAgICBzID0gImxhbWJkYS5taW4iLCAjIG9yICJsYW1iZGEuMXNlIgogICAgdHlwZSA9ICJyZXNwb25zZSIKICApCiAgCiAgIyBmaXQgYW5kIGV2YWx1YXRlIHJpZGdlIHJlZ3Jlc3Npb24KICByaWRnZV9maXQgPC0gZ2xtbmV0Ojpjdi5nbG1uZXQoCiAgICB4ID0gYXMubWF0cml4KGN2X3RyYWluX2RhdGEgfD4gZHBseXI6OnNlbGVjdCgtYmluYXJ5X2xhYmVsKSksCiAgICB5ID0gY3ZfdHJhaW5fZGF0YSRiaW5hcnlfbGFiZWwsCiAgICBmYW1pbHkgPSAiYmlub21pYWwiLAogICAgYWxwaGEgPSAwLAogICAgZm9sZGlkID0gYXMubnVtZXJpYyhhcy5mYWN0b3IoY3ZfdHJhaW5fZGF0YV9hbGwkYmxvY2tfaWQpKQogICkKICByaWRnZV9wcmVkcyA8LSBwcmVkaWN0KAogICAgcmlkZ2VfZml0LCAKICAgIGFzLm1hdHJpeChjdl92YWxpZF9kYXRhIHw+IGRwbHlyOjpzZWxlY3QoLWJpbmFyeV9sYWJlbCkpLCAKICAgIHMgPSAibGFtYmRhLm1pbiIsICMgb3IgImxhbWJkYS4xc2UiCiAgICB0eXBlID0gInJlc3BvbnNlIgogICkKICAKICAjIGZpdCByYW5kb20gZm9yZXN0CiAgcmZfZml0IDwtIHJhbmdlcjo6cmFuZ2VyKAogICAgYmluYXJ5X2xhYmVsIH4gLiwgZGF0YSA9IGN2X3RyYWluX2RhdGEsIAogICAgaW1wb3J0YW5jZSA9ICJpbXB1cml0eSIsICMgdG8gY29tcHV0ZSBNREkgaW1wb3J0YW5jZQogICAgcHJvYmFiaWxpdHkgPSBUUlVFLCB2ZXJib3NlID0gRkFMU0UKICApCiAgcmZfcHJlZHMgPC0gcHJlZGljdChyZl9maXQsIGN2X3ZhbGlkX2RhdGEpJHByZWRpY3Rpb25zWywgMl0KICAKICAjIGV2YWx1YXRlIHByZWRpY3Rpb25zCiAgcHJlZHNfbHMgPC0gbGlzdCgKICAgICJsb2dpc3RpYyIgPSBsb2dfcHJlZHMsCiAgICAibGFzc28iID0gbGFzc29fcHJlZHMsCiAgICAicmlkZ2UiID0gcmlkZ2VfcHJlZHMsCiAgICAicmYiID0gcmZfcHJlZHMKICApCiAgdmFsaWRfYXVyb2NfbHNbW2ZvbGRdXSA8LSBwdXJycjo6bWFwKAogICAgcHJlZHNfbHMsCiAgICB+IHlhcmRzdGljazo6cm9jX2F1Y192ZWMoCiAgICAgIHRydXRoID0gY3ZfdmFsaWRfZGF0YSRiaW5hcnlfbGFiZWwsIAogICAgICBlc3RpbWF0ZSA9IGMoLngpLCAKICAgICAgZXZlbnRfbGV2ZWwgPSAic2Vjb25kIgogICAgKQogICkgfD4KICAgIGRwbHlyOjpiaW5kX3Jvd3MoLmlkID0gIm1ldGhvZCIpCiAgdmFsaWRfYXVwcmNfbHNbW2ZvbGRdXSA8LSBwdXJycjo6bWFwKAogICAgcHJlZHNfbHMsCiAgICB+IHlhcmRzdGljazo6cHJfYXVjX3ZlYygKICAgICAgdHJ1dGggPSBjdl92YWxpZF9kYXRhJGJpbmFyeV9sYWJlbCwgCiAgICAgIGVzdGltYXRlID0gYygueCksIAogICAgICBldmVudF9sZXZlbCA9ICJzZWNvbmQiCiAgICApCiAgKSB8PgogICAgZHBseXI6OmJpbmRfcm93cyguaWQgPSAibWV0aG9kIikKICAKICAjIHNhdmUgZm9sZCBwcmVkaWN0aW9ucyBmb3IgZnV0dXJlIGludmVzdGlnYXRpb24KICB2YWxpZF9wcmVkc19sc1tbZm9sZF1dIDwtIGN2X3ZhbGlkX2RhdGFfYWxsIHw+IAogICAgZHBseXI6OmJpbmRfY29scyhwcmVkc19scykKICAKICAjIHNhdmUgZml0dGVkIG1vZGVscwogIHZhbGlkX2ZpdHNfbHNbW2ZvbGRdXSA8LSBsaXN0KAogICAgImxvZ2lzdGljIiA9IGxvZ19maXQsCiAgICAibGFzc28iID0gbGFzc29fZml0LAogICAgInJpZGdlIiA9IHJpZGdlX2ZpdCwKICAgICJyZiIgPSByZl9maXQKICApCiAgCiAgIyBzYXZlIFggc3RhbmRhcmQgZGV2aWF0aW9ucyB0byBub3JtYWxpemUgcmlkZ2UvbGFzc28gY29lZmZpY2llbnRzIGxhdGVyCiAgdmFsaWRfWF9zZHNfbHNbW2ZvbGRdXSA8LSBjdl90cmFpbl9kYXRhIHw+IAogICAgZHBseXI6OnNlbGVjdCgtYmluYXJ5X2xhYmVsKSB8PiAKICAgIGFwcGx5KDIsIHNkKQp9CgojIGV4YW1pbmUgdmFsaWRhdGlvbiBhY2N1cmFjeQp2YWxpZF9hdXJvY19kZiA8LSBkcGx5cjo6YmluZF9yb3dzKHZhbGlkX2F1cm9jX2xzLCAuaWQgPSAiZm9sZCIpCm1lYW5fdmFsaWRfYXVyb2NfZGYgPC0gdmFsaWRfYXVyb2NfZGYgfD4gCiAgZHBseXI6OnN1bW1hcmlzZShkcGx5cjo6YWNyb3NzKC1mb2xkLCB+IG1lYW4oLngsIG5hLnJtID0gVFJVRSkpKQp2YWxpZF9hdXByY19kZiA8LSBkcGx5cjo6YmluZF9yb3dzKHZhbGlkX2F1cHJjX2xzLCAuaWQgPSAiZm9sZCIpCm1lYW5fdmFsaWRfYXVwcmNfZGYgPC0gdmFsaWRfYXVwcmNfZGYgfD4gCiAgZHBseXI6OnN1bW1hcmlzZShkcGx5cjo6YWNyb3NzKC1mb2xkLCB+IG1lYW4oLngsIG5hLnJtID0gVFJVRSkpKQoKIyBldmFsdWF0ZSBiZXN0IG1vZGVsIG9uIHRlc3Qgc2V0CnRyYWluX2RhdGEgPC0gdHJhaW5fZGF0YV9hbGwgfD4gCiAgZHBseXI6OnNlbGVjdCh0aWR5c2VsZWN0OjphbGxfb2Yoa2VlcF92YXJzKSkKdGVzdF9kYXRhIDwtIHRlc3RfZGF0YV9hbGwgfD4KICBkcGx5cjo6c2VsZWN0KHRpZHlzZWxlY3Q6OmFsbF9vZihrZWVwX3ZhcnMpKQpiZXN0X2ZpdCA8LSByYW5nZXI6OnJhbmdlcigKICBiaW5hcnlfbGFiZWwgfiAuLCBkYXRhID0gdHJhaW5fZGF0YSwgcHJvYmFiaWxpdHkgPSBUUlVFLCB2ZXJib3NlID0gRkFMU0UKKQp0ZXN0X3ByZWRzIDwtIHByZWRpY3QoYmVzdF9maXQsIHRlc3RfZGF0YSkkcHJlZGljdGlvbnNbLCAyXQp0ZXN0X2F1cm9jIDwtIHlhcmRzdGljazo6cm9jX2F1Y192ZWMoCiAgdHJ1dGggPSB0ZXN0X2RhdGEkYmluYXJ5X2xhYmVsLCAKICBlc3RpbWF0ZSA9IHRlc3RfcHJlZHMsIAogIGV2ZW50X2xldmVsID0gInNlY29uZCIKKQp0ZXN0X2F1cHJjIDwtIHlhcmRzdGljazo6cHJfYXVjX3ZlYygKICB0cnV0aCA9IHRlc3RfZGF0YSRiaW5hcnlfbGFiZWwsIAogIGVzdGltYXRlID0gdGVzdF9wcmVkcywgCiAgZXZlbnRfbGV2ZWwgPSAic2Vjb25kIgopCgphdXJvY19kZiA8LSBtZWFuX3ZhbGlkX2F1cm9jX2RmIHw+CiAgZHBseXI6OnJlbmFtZV93aXRoKH4gc3ByaW50ZigiVmFsaWRhdGlvbiAlcyBBVVJPQyIsIHN0cmluZ3I6OnN0cl90b190aXRsZSgueCkpKSB8PiAKICBkcGx5cjo6bXV0YXRlKAogICAgYFRlc3QgQVVST0NgID0gdGVzdF9hdXJvYwogICkKYXVwcmNfZGYgPC0gbWVhbl92YWxpZF9hdXByY19kZiB8PgogIGRwbHlyOjpyZW5hbWVfd2l0aCh+IHNwcmludGYoIlZhbGlkYXRpb24gJXMgQVVQUkMiLCBzdHJpbmdyOjpzdHJfdG9fdGl0bGUoLngpKSkgfD4gCiAgZHBseXI6Om11dGF0ZSgKICAgIGBUZXN0IEFVUFJDYCA9IHRlc3RfYXVwcmMKICApCgp2dGhlbWVzOjpwcmV0dHlfa2FibGUoCiAgdmFsaWRfYXVyb2NfZGYsIGNhcHRpb24gPSAiRm9sZC13aXNlIFZhbGlkYXRpb24gQVVST0MgZm9yIFZhcmlvdXMgTW9kZWxzIgopCnZ0aGVtZXM6OnByZXR0eV9rYWJsZSgKICBhdXJvY19kZiwgY2FwdGlvbiA9ICJPdmVyYWxsIEFVUk9DIFByZWRpY3Rpb24gUGVyZm9ybWFuY2UiCikKdnRoZW1lczo6cHJldHR5X2thYmxlKAogIHZhbGlkX2F1cHJjX2RmLCBjYXB0aW9uID0gIkZvbGQtd2lzZSBWYWxpZGF0aW9uIEFVUFJDIGZvciBWYXJpb3VzIE1vZGVscyIKKQp2dGhlbWVzOjpwcmV0dHlfa2FibGUoCiAgYXVwcmNfZGYsIGNhcHRpb24gPSAiT3ZlcmFsbCBBVVBSQyBQcmVkaWN0aW9uIFBlcmZvcm1hbmNlIgopCmBgYAoKIyMjIFBvc3QtaG9jIGludmVzdGlnYXRpb25zIHsudGFic2V0IC50YWJzZXQtcGlsbHMgLnRhYnNldC1waWxscy1zcXVhcmV9CgpMZXQncyBsb29rIGF0IHRoZSBoZWxkLW91dCBmb2xkcyB3aGVyZSB0aGUgbWV0aG9kcyBkaWRuJ3QgcGVyZm9ybSBzbyB3ZWxsIGFuZCBjb21wYXJlIHRoZSBwcmVkaWN0aW9ucyBhY3Jvc3MgbWV0aG9kcy4KCmBgYHtyIHJlc3VsdHMgPSAiYXNpcyJ9CnBsb3RfdmFycyA8LSBjKAogICJiaW5hcnlfbGFiZWwiLCAibG9naXN0aWMiLCAibGFzc28iLCAicmlkZ2UiLCAicmYiLAogICJERiIsICJDRiIsICJCRiIsICJBRiIsICJBTiIsICJOREFJIiwgIlNEIiwgIkNPUlIiCikKZm9yIChmb2xkIGluIG5hbWVzKHZhbGlkX3ByZWRzX2xzKSkgewogIGNhdChzcHJpbnRmKCJcblxuIyMjIyBGb2xkICVzXG5cbiIsIGZvbGQpKQogIHByZWRzX2RmIDwtIHZhbGlkX3ByZWRzX2xzW1tmb2xkXV0KICBwbHRfbHMgPC0gbGlzdCgpCiAgZm9yICh2YXIgaW4gcGxvdF92YXJzKSB7CiAgICBwbHRfbHNbW3Zhcl1dIDwtIHBsb3RfY2xvdWRfZGF0YShwcmVkc19kZiwgdmFyKQogIH0KICBwbHQgPC0gcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHBsdF9scywgbmNvbCA9IDUpCiAgdnRoZW1lczo6c3ViY2h1bmtpZnkoCiAgICBwbHQsIGkgPSBzdWJjaHVua19pZHgsIGZpZ193aWR0aCA9IDE0LCBmaWdfaGVpZ2h0ID0gNgogICkKICBzdWJjaHVua19pZHggPC0gc3ViY2h1bmtfaWR4ICsgMQp9CmBgYAoKCiMgSW50ZXJwcmV0YXRpb25zIHsudGFic2V0IC50YWJzZXQtdm1vZGVybn0KCmBgYHtyIHJlc3VsdHMgPSAiYXNpcyJ9CmZpX2xzIDwtIGxpc3QoKQpmb3IgKGZvbGQgaW4gbmFtZXModmFsaWRfZml0c19scykpIHsKICAjIGdldCBmZWF0dXJlIGltcG9ydGFuY2VzIGZyb20gZWFjaCBtZXRob2QKICBsb2dfZml0IDwtIHZhbGlkX2ZpdHNfbHNbW2ZvbGRdXSRsb2dpc3RpYwogIGxvZ19maV9kZjEgPC0gdGliYmxlOjp0aWJibGUoCiAgICBtZXRob2QgPSAibG9naXN0aWMgKGNvZWYpIiwKICAgIHZhciA9IG5hbWVzKGNvZWYobG9nX2ZpdCkpWy0xXSwgIyByZW1vdmUgZmlyc3QgZWxlbWVudCAoaS5lLiwgaW50ZXJjZXB0KQogICAgaW1wb3J0YW5jZSA9IGNvZWYobG9nX2ZpdClbLTFdCiAgKQogIGxvZ19maV9kZjIgPC0gdGliYmxlOjp0aWJibGUoCiAgICBtZXRob2QgPSAibG9naXN0aWMgKHogc3RhdCkiLAogICAgdmFyID0gbmFtZXMoc3VtbWFyeShsb2dfZml0KSRjb2VmZmljaWVudHNbLCAieiB2YWx1ZSJdKVstMV0sCiAgICBpbXBvcnRhbmNlID0gc3VtbWFyeShsb2dfZml0KSRjb2VmZmljaWVudHNbLCAieiB2YWx1ZSJdWy0xXSwKICApCiAgCiAgbGFzc29fZml0IDwtIHZhbGlkX2ZpdHNfbHNbW2ZvbGRdXSRsYXNzbwogIGxhc3NvX2ZpX2RmMSA8LSB0aWJibGU6OnRpYmJsZSgKICAgIG1ldGhvZCA9ICJsYXNzbyAoY29lZikiLAogICAgdmFyID0gcm93bmFtZXMoY29lZihsYXNzb19maXQsIHMgPSAibGFtYmRhLm1pbiIpKVstMV0sCiAgICBpbXBvcnRhbmNlID0gYXMubWF0cml4KGNvZWYobGFzc29fZml0LCBzID0gImxhbWJkYS5taW4iKSlbLTFdCiAgKQogIGxhc3NvX2ZpX2RmMiA8LSB0aWJibGU6OnRpYmJsZSgKICAgIG1ldGhvZCA9ICJsYXNzbyAoc3RkLiBjb2VmKSIsCiAgICB2YXIgPSByb3duYW1lcyhjb2VmKGxhc3NvX2ZpdCwgcyA9ICJsYW1iZGEubWluIikpWy0xXSwKICAgIGltcG9ydGFuY2UgPSBhcy5tYXRyaXgoY29lZihsYXNzb19maXQsIHMgPSAibGFtYmRhLm1pbiIpKVstMV0gKgogICAgICB2YWxpZF9YX3Nkc19sc1tbZm9sZF1dCiAgKQogIAogIHJpZGdlX2ZpdCA8LSB2YWxpZF9maXRzX2xzW1tmb2xkXV0kcmlkZ2UKICByaWRnZV9maV9kZjEgPC0gdGliYmxlOjp0aWJibGUoCiAgICBtZXRob2QgPSAicmlkZ2UgKGNvZWYpIiwKICAgIHZhciA9IHJvd25hbWVzKGNvZWYocmlkZ2VfZml0LCBzID0gImxhbWJkYS5taW4iKSlbLTFdLAogICAgaW1wb3J0YW5jZSA9IGFzLm1hdHJpeChjb2VmKHJpZGdlX2ZpdCwgcyA9ICJsYW1iZGEubWluIikpWy0xXQogICkKICByaWRnZV9maV9kZjIgPC0gdGliYmxlOjp0aWJibGUoCiAgICBtZXRob2QgPSAicmlkZ2UgKHN0ZC4gY29lZikiLAogICAgdmFyID0gcm93bmFtZXMoY29lZihyaWRnZV9maXQsIHMgPSAibGFtYmRhLm1pbiIpKVstMV0sCiAgICBpbXBvcnRhbmNlID0gYXMubWF0cml4KGNvZWYocmlkZ2VfZml0LCBzID0gImxhbWJkYS5taW4iKSlbLTFdICoKICAgICAgdmFsaWRfWF9zZHNfbHNbW2ZvbGRdXQogICkKICAKICByZl9maXQgPC0gdmFsaWRfZml0c19sc1tbZm9sZF1dJHJmCiAgcmZfZmlfZGYxIDwtIHRpYmJsZTo6dGliYmxlKAogICAgbWV0aG9kID0gInJmIChtZGkpIiwKICAgIHZhciA9IG5hbWVzKHJmX2ZpdCR2YXJpYWJsZS5pbXBvcnRhbmNlKSwKICAgIGltcG9ydGFuY2UgPSByZl9maXQkdmFyaWFibGUuaW1wb3J0YW5jZQogICkKICAjIGNvbXB1dGUgcGVybXV0YXRpb24gaW1wb3J0YW5jZSB1c2luZyBoZWxkLW91dCBmb2xkIChub3QgdHJhaW5pbmcgZm9sZHMpCiAgWF92YWxpZF9kZiA8LSB0cmFpbl9kYXRhX2FsbCB8PgogICAgZHBseXI6OmZpbHRlcihibG9ja19pZCA9PSAhIWZvbGQpIHw+CiAgICBkcGx5cjo6c2VsZWN0KHRpZHlzZWxlY3Q6OmFsbF9vZihrZWVwX3ZhcnMpKQogIHBmdW4gPC0gZnVuY3Rpb24ob2JqZWN0LCBuZXdkYXRhKSB7CiAgICAjIE5lZWRzIHRvIHJldHVybiB2ZWN0b3Igb2YgY2xhc3MgcHJlZGljdGlvbnMgZnJvbSBhIHJhbmdlciBvYmplY3QgKGlmIHVzaW5nIG1ldHJpYyA9ICJhY2N1cmFjeSIpCiAgICBpZmVsc2UoCiAgICAgIHByZWRpY3Qob2JqZWN0LCBkYXRhID0gbmV3ZGF0YSkkcHJlZGljdGlvbnNbLCAyXSA+PSAwLjUsCiAgICAgICJDbG91ZHMiLAogICAgICAiTm8gQ2xvdWRzIgogICAgKSB8PgogICAgICBmYWN0b3IobGV2ZWxzID0gYygiTm8gQ2xvdWRzIiwgIkNsb3VkcyIpKQogIH0KICBwZXJtdXRlX2ltcCA8LSB2aXA6OnZpKAogICAgcmZfZml0LCBtZXRob2QgPSAicGVybXV0ZSIsIHRyYWluID0gWF92YWxpZF9kZiwgdGFyZ2V0ID0gImJpbmFyeV9sYWJlbCIsCiAgICBtZXRyaWMgPSAiYWNjdXJhY3kiLCBwcmVkX3dyYXBwZXIgPSBwZnVuLCBuc2ltID0gMTAKICApCiAgcmZfZmlfZGYyIDwtIHRpYmJsZTo6dGliYmxlKAogICAgbWV0aG9kID0gInJmIChwZXJtdXRhdGlvbikiLAogICAgdmFyID0gcGVybXV0ZV9pbXAkVmFyaWFibGUsCiAgICBpbXBvcnRhbmNlID0gcGVybXV0ZV9pbXAkSW1wb3J0YW5jZQogICkKICAKICBmaV9sc1tbZm9sZF1dIDwtIGRwbHlyOjpiaW5kX3Jvd3MoCiAgICBsb2dfZmlfZGYxLCBsb2dfZmlfZGYyLCAKICAgIGxhc3NvX2ZpX2RmMSwgbGFzc29fZmlfZGYyLCAKICAgIHJpZGdlX2ZpX2RmMSwgcmlkZ2VfZmlfZGYyLCAKICAgIHJmX2ZpX2RmMSwgcmZfZmlfZGYyCiAgKQp9CgpmaV9kZiA8LSBkcGx5cjo6YmluZF9yb3dzKGZpX2xzLCAuaWQgPSAiZm9sZCIpIHw+IAogIGRwbHlyOjptdXRhdGUoCiAgICB2YXIgPSBmYWN0b3IodmFyLCBsZXZlbHMgPSBrZWVwX3ZhcnMpCiAgKQoKZm9yIChtZXRob2RfbmFtZSBpbiBjKCJsb2dpc3RpYyIsICJsYXNzbyIsICJyaWRnZSIsICJyZiIpKSB7CiAgY2F0KAogICAgc3ByaW50ZigiXG5cbiMjICVzIHsudGFic2V0IC50YWJzZXQtcGlsbHMgLnRhYnNldC1waWxscy1zcXVhcmV9XG5cbiIsIG1ldGhvZF9uYW1lKQogICkKICAKICBwbHRfZGYgPC0gZmlfZGYgfD4gCiAgICBkcGx5cjo6ZmlsdGVyKAogICAgICBzdHJpbmdyOjpzdHJfc3RhcnRzKG1ldGhvZCwgISFtZXRob2RfbmFtZSkKICAgICkKICAKICAjIGJhciBwbG90CiAgaWYgKG1ldGhvZF9uYW1lICVpbiUgYygibG9naXN0aWMiLCAibGFzc28iLCAicmlkZ2UiKSkgewogICAgcGx0IDwtIHBsdF9kZiB8PiAKICAgICAgZHBseXI6Omdyb3VwX2J5KG1ldGhvZCwgdmFyKSB8PiAKICAgICAgZHBseXI6OnN1bW1hcmlzZSgKICAgICAgICBtZWFuX2ltcG9ydGFuY2UgPSBtZWFuKGltcG9ydGFuY2UpLAogICAgICAgIHNlX2ltcG9ydGFuY2UgPSBzZChpbXBvcnRhbmNlKSAvIHNxcnQoZHBseXI6Om4oKSksCiAgICAgICAgLmdyb3VwcyA9ICJkcm9wIgogICAgICApIHw+IAogICAgICBkcGx5cjo6ZmlsdGVyKAogICAgICAgIHN0cmluZ3I6OnN0cl9kZXRlY3QobWV0aG9kLCAiXFwoY29lZlxcKSIpCiAgICAgICkgfD4gCiAgICAgIGdncGxvdDI6OmdncGxvdCgpICsKICAgICAgZ2dwbG90Mjo6Z2VvbV9iYXIoCiAgICAgICAgZ2dwbG90Mjo6YWVzKHggPSB2YXIsIHkgPSBtZWFuX2ltcG9ydGFuY2UpLAogICAgICAgIHN0YXQgPSAiaWRlbnRpdHkiCiAgICAgICkgKwogICAgICBnZ3Bsb3QyOjpnZW9tX2Vycm9yYmFyKAogICAgICAgIGdncGxvdDI6OmFlcygKICAgICAgICAgIHggPSB2YXIsIAogICAgICAgICAgeW1pbiA9IG1lYW5faW1wb3J0YW5jZSAtIHNlX2ltcG9ydGFuY2UsCiAgICAgICAgICB5bWF4ID0gbWVhbl9pbXBvcnRhbmNlICsgc2VfaW1wb3J0YW5jZQogICAgICAgICksCiAgICAgICAgd2lkdGggPSAwCiAgICAgICkgKwogICAgICBnZ3Bsb3QyOjpmYWNldF93cmFwKH4gbWV0aG9kLCBuY29sID0gMSwgc2NhbGVzID0gImZyZWVfeSIpICsKICAgICAgZ2dwbG90Mjo6bGFicyh4ID0gIkZlYXR1cmUiLCB5ID0gIk1lYW4gSW1wb3J0YW5jZSIpICsKICAgICAgdnRoZW1lczo6dGhlbWVfdm1vZGVybigpCiAgICAKICAgIHZ0aGVtZXM6OnN1YmNodW5raWZ5KAogICAgICBwbHQsIGkgPSBzdWJjaHVua19pZHgsIGZpZ193aWR0aCA9IDEyLCBmaWdfaGVpZ2h0ID0gNAogICAgKQogICAgc3ViY2h1bmtfaWR4IDwtIHN1YmNodW5rX2lkeCArIDEKICB9CiAgCiAgIyBiYXIgcGxvdAogIHBsdCA8LSBwbHRfZGYgfD4gCiAgICBkcGx5cjo6Z3JvdXBfYnkobWV0aG9kLCB2YXIpIHw+IAogICAgZHBseXI6OnN1bW1hcmlzZSgKICAgICAgbWVhbl9pbXBvcnRhbmNlID0gbWVhbihpbXBvcnRhbmNlKSwKICAgICAgc2VfaW1wb3J0YW5jZSA9IHNkKGltcG9ydGFuY2UpIC8gc3FydChkcGx5cjo6bigpKSwKICAgICAgLmdyb3VwcyA9ICJkcm9wIgogICAgKSB8PiAKICAgIGdncGxvdDI6OmdncGxvdCgpICsKICAgIGdncGxvdDI6Omdlb21fYmFyKAogICAgICBnZ3Bsb3QyOjphZXMoeCA9IHZhciwgeSA9IG1lYW5faW1wb3J0YW5jZSksCiAgICAgIHN0YXQgPSAiaWRlbnRpdHkiCiAgICApICsKICAgIGdncGxvdDI6Omdlb21fZXJyb3JiYXIoCiAgICAgIGdncGxvdDI6OmFlcygKICAgICAgICB4ID0gdmFyLCAKICAgICAgICB5bWluID0gbWVhbl9pbXBvcnRhbmNlIC0gc2VfaW1wb3J0YW5jZSwKICAgICAgICB5bWF4ID0gbWVhbl9pbXBvcnRhbmNlICsgc2VfaW1wb3J0YW5jZQogICAgICApLAogICAgICB3aWR0aCA9IDAKICAgICkgKwogICAgZ2dwbG90Mjo6ZmFjZXRfd3JhcCh+IG1ldGhvZCwgbmNvbCA9IDEsIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgICBnZ3Bsb3QyOjpsYWJzKHggPSAiRmVhdHVyZSIsIHkgPSAiTWVhbiBJbXBvcnRhbmNlIikgKwogICAgdnRoZW1lczo6dGhlbWVfdm1vZGVybigpCiAgdnRoZW1lczo6c3ViY2h1bmtpZnkoCiAgICBwbG90bHk6OmdncGxvdGx5KHBsdCksIGkgPSBzdWJjaHVua19pZHgsIGZpZ193aWR0aCA9IDEyLCBmaWdfaGVpZ2h0ID0gOAogICkKICBzdWJjaHVua19pZHggPC0gc3ViY2h1bmtfaWR4ICsgMQogIAogICMgYm94cGxvdAogIHBsdCA8LSBwbHRfZGYgfD4gCiAgICBnZ3Bsb3QyOjpnZ3Bsb3QoKSArCiAgICBnZ3Bsb3QyOjpnZW9tX2JveHBsb3QoCiAgICAgIGdncGxvdDI6OmFlcyh4ID0gdmFyLCB5ID0gaW1wb3J0YW5jZSkKICAgICkgKwogICAgZ2dwbG90Mjo6ZmFjZXRfd3JhcCh+IG1ldGhvZCwgbmNvbCA9IDEsIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgICBnZ3Bsb3QyOjpsYWJzKHggPSAiRmVhdHVyZSIsIHkgPSAiSW1wb3J0YW5jZSIpICsKICAgIHZ0aGVtZXM6OnRoZW1lX3Ztb2Rlcm4oKQogIHZ0aGVtZXM6OnN1YmNodW5raWZ5KAogICAgcGx0LCBpID0gc3ViY2h1bmtfaWR4LCBmaWdfd2lkdGggPSAxMiwgZmlnX2hlaWdodCA9IDEwCiAgKQogIHN1YmNodW5rX2lkeCA8LSBzdWJjaHVua19pZHggKyAxCn0KCiMgc3VtbWFyeSBvZiAobm9ybWFsaXplZCkgZmVhdHVyZSBpbXBvcnRhbmNlIHJlc3VsdHMgd2l0aG91dCBhYnNvbHV0ZSB2YWx1ZQpjYXQoIlxuXG4jIyBTdW1tYXJ5IHsudGFic2V0IC50YWJzZXQtcGlsbHMgLnRhYnNldC1waWxscy1zcXVhcmV9XG5cbiIpCmtlZXBfbWV0aG9kcyA8LSBjKAogICJsb2dpc3RpYyAoeiBzdGF0KSIsICJsYXNzbyAoc3RkLiBjb2VmKSIsICJyaWRnZSAoc3RkLiBjb2VmKSIsICJyZiAobWRpKSIsICJyZiAocGVybXV0YXRpb24pIgopCnBsdCA8LSBmaV9kZiB8PiAKICBkcGx5cjo6ZmlsdGVyKAogICAgbWV0aG9kICVpbiUgISFrZWVwX21ldGhvZHMKICApIHw+IAogIGRwbHlyOjptdXRhdGUoCiAgICBtZXRob2QgPSBmYWN0b3IobWV0aG9kLCBsZXZlbHMgPSBrZWVwX21ldGhvZHMpCiAgKSB8PiAKICBkcGx5cjo6Z3JvdXBfYnkobWV0aG9kLCB2YXIpIHw+IAogIGRwbHlyOjpzdW1tYXJpc2UoCiAgICBtZWFuX2ltcG9ydGFuY2UgPSBtZWFuKGltcG9ydGFuY2UpLAogICAgc2VfaW1wb3J0YW5jZSA9IHNkKGltcG9ydGFuY2UpIC8gc3FydChkcGx5cjo6bigpKSwKICAgIC5ncm91cHMgPSAiZHJvcCIKICApIHw+IAogIGdncGxvdDI6OmdncGxvdCgpICsKICBnZ3Bsb3QyOjpnZW9tX2JhcigKICAgIGdncGxvdDI6OmFlcyh4ID0gdmFyLCB5ID0gbWVhbl9pbXBvcnRhbmNlKSwKICAgIHN0YXQgPSAiaWRlbnRpdHkiCiAgKSArCiAgZ2dwbG90Mjo6Z2VvbV9lcnJvcmJhcigKICAgIGdncGxvdDI6OmFlcygKICAgICAgeCA9IHZhciwgCiAgICAgIHltaW4gPSBtZWFuX2ltcG9ydGFuY2UgLSBzZV9pbXBvcnRhbmNlLAogICAgICB5bWF4ID0gbWVhbl9pbXBvcnRhbmNlICsgc2VfaW1wb3J0YW5jZQogICAgKSwKICAgIHdpZHRoID0gMAogICkgKwogIGdncGxvdDI6OmZhY2V0X3dyYXAofiBtZXRob2QsIG5jb2wgPSAxLCBzY2FsZXMgPSAiZnJlZV95IikgKwogIGdncGxvdDI6OmxhYnMoeCA9ICJGZWF0dXJlIiwgeSA9ICJNZWFuIEltcG9ydGFuY2UiKSArCiAgdnRoZW1lczo6dGhlbWVfdm1vZGVybigpCnZ0aGVtZXM6OnN1YmNodW5raWZ5KAogIHBsb3RseTo6Z2dwbG90bHkocGx0KSwgaSA9IHN1YmNodW5rX2lkeCwgZmlnX3dpZHRoID0gMTIsIGZpZ19oZWlnaHQgPSAxMgopCnN1YmNodW5rX2lkeCA8LSBzdWJjaHVua19pZHggKyAxCgojIHN1bW1hcnkgb2YgKG5vcm1hbGl6ZWQpIGZlYXR1cmUgaW1wb3J0YW5jZSByZXN1bHRzIHdpdGggYWJzb2x1dGUgdmFsdWUKY2F0KCJcblxuIyMgU3VtbWFyeSAobWFnbml0dWRlcyBvbmx5KSB7LnRhYnNldCAudGFic2V0LXBpbGxzIC50YWJzZXQtcGlsbHMtc3F1YXJlfVxuXG4iKQprZWVwX21ldGhvZF9sZXZlbHMgPC0gc3RyaW5ncjo6c3RyX3JlcGxhY2VfYWxsKGtlZXBfbWV0aG9kcywgInN0ZC4gY29lZiIsICJ8c3RkLiBjb2VmfCIpCnBsdCA8LSBmaV9kZiB8PiAKICBkcGx5cjo6ZmlsdGVyKAogICAgbWV0aG9kICVpbiUgISFrZWVwX21ldGhvZHMKICApIHw+IAogIGRwbHlyOjptdXRhdGUoCiAgICBpbXBvcnRhbmNlID0gZHBseXI6OmNhc2Vfd2hlbigKICAgICAgc3RyaW5ncjo6c3RyX2RldGVjdChtZXRob2QsICJsb2dpc3RpY3xsYXNzb3xyaWRnZSIpIH4gYWJzKGltcG9ydGFuY2UpLAogICAgICBUUlVFIH4gaW1wb3J0YW5jZQogICAgKSwKICAgIG1ldGhvZCA9IHN0cmluZ3I6OnN0cl9yZXBsYWNlX2FsbChtZXRob2QsICJzdGQuIGNvZWYiLCAifHN0ZC4gY29lZnwiKSB8PiAKICAgICAgZmFjdG9yKGxldmVscyA9IGtlZXBfbWV0aG9kX2xldmVscykKICApIHw+IAogIGRwbHlyOjpncm91cF9ieShtZXRob2QsIHZhcikgfD4gCiAgZHBseXI6OnN1bW1hcmlzZSgKICAgIG1lYW5faW1wb3J0YW5jZSA9IG1lYW4oaW1wb3J0YW5jZSksCiAgICBzZV9pbXBvcnRhbmNlID0gc2QoaW1wb3J0YW5jZSkgLyBzcXJ0KGRwbHlyOjpuKCkpLAogICAgLmdyb3VwcyA9ICJkcm9wIgogICkgfD4gCiAgZ2dwbG90Mjo6Z2dwbG90KCkgKwogIGdncGxvdDI6Omdlb21fYmFyKAogICAgZ2dwbG90Mjo6YWVzKHggPSB2YXIsIHkgPSBtZWFuX2ltcG9ydGFuY2UpLAogICAgc3RhdCA9ICJpZGVudGl0eSIKICApICsKICBnZ3Bsb3QyOjpnZW9tX2Vycm9yYmFyKAogICAgZ2dwbG90Mjo6YWVzKAogICAgICB4ID0gdmFyLCAKICAgICAgeW1pbiA9IG1lYW5faW1wb3J0YW5jZSAtIHNlX2ltcG9ydGFuY2UsCiAgICAgIHltYXggPSBtZWFuX2ltcG9ydGFuY2UgKyBzZV9pbXBvcnRhbmNlCiAgICApLAogICAgd2lkdGggPSAwCiAgKSArCiAgZ2dwbG90Mjo6ZmFjZXRfd3JhcCh+IG1ldGhvZCwgbmNvbCA9IDEsIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgZ2dwbG90Mjo6bGFicyh4ID0gIkZlYXR1cmUiLCB5ID0gIk1lYW4gSW1wb3J0YW5jZSIpICsKICB2dGhlbWVzOjp0aGVtZV92bW9kZXJuKCkKdnRoZW1lczo6c3ViY2h1bmtpZnkoCiAgcGxvdGx5OjpnZ3Bsb3RseShwbHQpLCBpID0gc3ViY2h1bmtfaWR4LCBmaWdfd2lkdGggPSAxMiwgZmlnX2hlaWdodCA9IDEyCikKc3ViY2h1bmtfaWR4IDwtIHN1YmNodW5rX2lkeCArIDEKYGBgCgo=